﻿using OperationPlugins.Applications.Models;
using OperationPlugins.Compositions;
using System;
using System.Collections.Concurrent;
using System.ComponentModel.Composition;
using System.Reflection;
using System.Web;

namespace OperationPlugins.Applications
{
    public static class ApplicationManager
    {
        /// <summary>
        /// The application data context.
        /// </summary>
        public static IApplicationDataContext ApplicationDataContext { get; set; }

        private static readonly ConcurrentDictionary<string, Sandbox> Sandboxes = new ConcurrentDictionary<string, Sandbox>();

        /// <summary>
        /// Creates a new IApplicationInstance for the specified request context.
        /// </summary>
        /// <param name="context">The request context.</param>
        /// <param name="hostAssembly">The host assembly.</param>
        /// <returns>The IApplicationInstance.</returns>
        public static IApplicationInstance CreateApplicationInstance(ApplicationRequestContext context, Assembly hostAssembly)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            // Resolve the AppId.
            string appId = context.Headers.AppId;
            if (string.IsNullOrEmpty(appId))
            {
                throw new ApplicationManagerException("The AppId is missing.");
            }

            // Load the application definition.
            AppInstance app = GetAppInstaceByAppId(appId);

            // Create a new application instance and login.
            ApplicationInstanceImpl instance = new ApplicationInstanceImpl(context, app);

            // The Login method throws an exception if login fails, which is ok!
            instance.Login();

            // Create a new or use an existing Sandbox.
            Sandbox sandbox;
            if (Sandboxes.TryGetValue(app.Id, out sandbox) == false)
            {
                sandbox = Sandbox.CreateSandbox(app, hostAssembly);
                if (Sandboxes.TryAdd(app.Id, sandbox) == false)
                {
                    throw new ApplicationManagerException("Failed to cache the sandbox.");
                }
            }
            instance.Sandbox = sandbox;

            return instance;
        }

        public static AppInstance GetAppInstaceByAppId(string appId)
        {
            if (appId == null)
            {
                throw new ArgumentNullException("appId");
            }

            AppInstance app = null;

            if (HttpContext.Current != null)
            {
                app = HttpContext.Current.Items["AppInstance"] as AppInstance;
            }

            if (app == null)
            {
                try
                {
                    app = ApplicationDataContext.GetAppInstaceByAppId(appId);
                }
                catch (Exception ex)
                {
                    throw new ApplicationManagerException(string.Format("Failed to load the definition for the application {0}.", appId), ex);
                }

                if (app == null)
                {
                    throw new ApplicationManagerException(string.Format("{0} is unknown application.", appId));
                }

                if (HttpContext.Current != null)
                {
                    HttpContext.Current.Items["AppInstance"] = app;   
                }
            }

            return app;
        }

        /// <summary>
        /// Initialize.
        /// </summary>
        [Export(typeof(IInitialize))]
        private sealed class Initializer : IInitialize
        {
            void IInitialize.Initialize(InitializationContext context)
            {
                ApplicationDataContext = context.CompositionContainer.GetPlugin<IApplicationDataContext>();
            }
        }

    }
}